/* Copyright (c) 2004-2008, Andriy Nikolov
 * All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.processmining.email;

import java.awt.*;
import javax.swing.*;
import com.borland.jbcl.layout.*;
import java.awt.event.*;
import java.util.*;

/**
 * <p>The frame displaying the sociogram in a standalone version</p>
 * @author Andriy Nikolov
 * @version 1.0
 */

public class SociogramFrame extends JFrame {
  Sociogram sociogram;

  JScrollPane jScrollPane1 = new JScrollPane();
  GraphPanel jPanel1 = new GraphPanel();
  public SociogramFrame() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
  private void jbInit() throws Exception {

    this.setSize(700, 500);
    this.setTitle("Sociogram");
    jPanel1.setLayout(null);
    jPanel1.setSize(700, 480);
    jPanel1.setBackground(Color.white);
    jScrollPane1.getViewport().add(jPanel1, null);
    this.getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);

  }

  public void setSociogram(Sociogram sociogram) {
    this.sociogram = sociogram;
    jPanel1.setSociogram(sociogram);
  }

  public void displaySociogram() {
    jPanel1.repaint();
  }
}

class GraphPanel extends JPanel {
  Sociogram sociogram;
  LinkedList nodes, arcs;
  Point center;
  int radius;
  Node nodeBeingCarried = null;

  public GraphPanel() {
    super();
    nodes = new LinkedList();
    arcs = new LinkedList();
    addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseReleased(MouseEvent e) {
        dropNode(e);
     }
    });
    addMouseMotionListener(new java.awt.event.MouseMotionAdapter(){
      public void mouseDragged(MouseEvent e) {
        carryNode(e);
      }
    });
  }

  public void setSociogram(Sociogram soc) {
    sociogram = soc;
    createNodes();
    createArcs();
  }

  public Sociogram getSociogram() {
    return sociogram;
  }

  public void paintComponent(Graphics g) {
    Arc iterArc;
    super.paintComponent(g);
    for(int i=0;i<arcs.size();i++) {
      iterArc = (Arc)arcs.get(i);
      iterArc.paint(g);
    }
  }

  private void calculateCenter() {
    center = new Point();
    center.x = new Double(getSize().getWidth()/2).intValue();
    center.y = new Double(getSize().getHeight()/2).intValue();
    radius = center.y-20;
  }

  public void setSize(int width, int height) {
    super.setSize(width, height);
    calculateCenter();
  }

  public void setSize(Dimension d) {
    super.setSize(d);
    calculateCenter();
  }

  private void createNodes() {
    Node curNode;
    Point nodeCenter;
    EMailRecipient tmp;
    nodes = new LinkedList();

    LinkedList emplList = sociogram.getEmployeesList();
    for(int i=0;i<emplList.size();i++) {
      tmp = (EMailRecipient)emplList.get(i);
      curNode = new Node(tmp.getName(), this);
      nodeCenter = calculateNodeCenter(i, emplList.size());
      curNode.setCenter(nodeCenter);
      nodes.add(curNode);
      add(curNode, null);
    }
  }

  private Point calculateNodeCenter(int cur, int total) {
    double cx, cy;

    double angle = 2*Math.PI*cur/total;
    cx = center.x + radius*Math.sin(angle);
    cy = center.y - radius*Math.cos(angle);
    return new Point(new Double(cx).intValue(), new Double(cy).intValue());
  }

  private void createArcs() {
    double weight;
    Arc curArc;
    Node from, to;
    arcs = new LinkedList();
    for (int i=0;i<nodes.size();i++) {
      for (int j=0;j<nodes.size();j++) {
        weight = sociogram.getWeight(i, j);
        if(weight>0) {
          from = (Node)nodes.get(i);
          to = (Node)nodes.get(j);
          curArc = new Arc();
          curArc.setWeight(weight);
          curArc.setFrom(from);
          curArc.setTo(to);
          if(sociogram.getWeight(j, i)>0) {
            if(i<j) {
              curArc.setOffsetFrom(-5, -2);
              curArc.setOffsetTo(-5, -2);
            } else {
              curArc.setOffsetTo(5, 2);
              curArc.setOffsetFrom(5, 2);
            }
          }
          arcs.add(curArc);
        }
      }
    }
  }

  private void dropNode(MouseEvent e) {
//    System.out.println("carry: "+e.getX()+":"+e.getY());
    if(nodeBeingCarried!=null) {
      nodeBeingCarried.stopCarry(e);
    }
  }

  private void carryNode(MouseEvent e) {
//    System.out.println("carryNode: "+e.getX()+":"+e.getY());
    if(nodeBeingCarried!=null) {
      nodeBeingCarried.carry(e);
    }
  }

  public void setNodeBeingCarried(Node node) {
    nodeBeingCarried = node;
  }

  public Node getNodeBeingCarried() {
    return nodeBeingCarried;
  }

}

class Node extends JButton {
  Point center;
  int preferredWidth;
  GraphPanel parentPanel;
  Point offset;
  public LinkedList arcsFrom;
  public LinkedList arcsTo;

  public final static int NODE_WIDTH = 80;
  public final static int NODE_HEIGHT = 24;
  public static int scale = 1;

  public Node(String name, GraphPanel parentPanel) {
    super(name);

    this.parentPanel = parentPanel;
    FontMetrics fontMetrics;
    arcsFrom = new LinkedList();
    arcsTo = new LinkedList();
    fontMetrics = parentPanel.getFontMetrics(parentPanel.getFont());
    preferredWidth = NODE_WIDTH;
    if(fontMetrics.stringWidth(name)+40*scale>NODE_WIDTH*scale) {
      preferredWidth = fontMetrics.stringWidth(name)+40*scale;
    }
    setPreferredSize(new Dimension(preferredWidth*scale, NODE_HEIGHT*scale));
    setBackground(Color.orange);
    addMouseListener(new java.awt.event.MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        startCarry(e);
      }
      public void mouseReleased(MouseEvent e) {
        stopCarry(e);
      }
    });
  }

  public void setParentPanel(GraphPanel parentPanel) {
    this.parentPanel = parentPanel;
  }

  public void setCenter(Point center) {
    this.center = center;
    setBounds(center.x-preferredWidth*scale/2, center.y-NODE_HEIGHT*scale/2, preferredWidth*scale, NODE_HEIGHT*scale);
  }

  public Point getCenter() {
    return center;
  }

  public void startCarry(MouseEvent e) {
    offset = e.getPoint();
    parentPanel.setNodeBeingCarried(this);
//    System.out.println("startCarry: "+e.getX()+":"+e.getY());
  }

  public void carry(MouseEvent e) {
    setCenter(e.getPoint());
    repaint();
//    System.out.println("carry: "+e.getX()+":"+e.getY());
  }

  public void stopCarry(MouseEvent e) {
//    System.out.println("stopCarry: "+e.getX()+":"+e.getY());
    setCenter(new Point(getCenter().x+e.getX()-offset.x, getCenter().y+e.getY()-offset.y));
    parentPanel.setNodeBeingCarried(null);
    parentPanel.repaint();
  }
}

class Arc {
  Node nodeFrom, nodeTo;
  double weight;
  Point offsetFrom, offsetTo;
  Point absoluteFrom, absoluteTo;
  Point arrowhead;
  Point pointArrowLeft;
  Point pointArrowRight;
  Point pointCenter;
  Point pointOneThird;

  public Arc() {
    offsetFrom = new Point(0, 0);
    offsetTo = new Point(0, 0);
  }

  public void setFrom(Node from) {
    nodeFrom = from;
  }

  public void setTo(Node to) {
    nodeTo = to;
  }

  public Node getFrom() {
    return nodeFrom;
  }

  public Node getTo() {
    return nodeTo;
  }

  public void setWeight(double weight) {
    this.weight = weight;
  }

  public double getWeight() {
    return weight;
  }

  public Point getOffsetFrom() {
    return offsetFrom;
  }

  public void setOffsetFrom(int x, int y) {
    offsetFrom = new Point(x, y);
  }

  public void setOffsetFrom(Point point) {
    offsetFrom = new Point(point);
  }

  public Point getOffsetTo() {
    return offsetTo;
  }

  public void setOffsetTo(int x, int y) {
    offsetTo = new Point(x, y);
  }

  public void setOffsetTo(Point point) {
    offsetTo = new Point(point);
  }

  public void paint(Graphics g) {
    // Draw line
    calculateAbsolutes();
    g.drawLine(absoluteFrom.x, absoluteFrom.y, absoluteTo.x, absoluteTo.y);
    // Draw arrow
    getArrowheadPoints();
    g.drawLine(arrowhead.x, arrowhead.y, pointArrowLeft.x, pointArrowLeft.y);
    g.drawLine(arrowhead.x, arrowhead.y, pointArrowRight.x, pointArrowRight.y);
    // Draw text
    g.drawString(Double.toString(weight), pointCenter.x, pointCenter.y);
  }

  private void getArrowheadPoints() {
    arrowhead = new Point();
    pointArrowLeft = new Point();
    pointArrowRight = new Point();
    pointCenter = new Point();
//    pointOneThird = new Point();
    double length, delta;
    Point arrowback = new Point();

    length = Math.sqrt(Math.pow((absoluteTo.y-absoluteFrom.y), 2)+Math.pow((absoluteTo.x-absoluteFrom.x), 2));

    if (absoluteTo.y!=absoluteFrom.y) {
      delta = (absoluteTo.x-absoluteFrom.x)/length;
      arrowhead.x = new Double(absoluteTo.x-delta*(Node.NODE_WIDTH/2)).intValue();
      arrowback.x = new Double(absoluteTo.x-delta*(Node.NODE_WIDTH/2+5*Node.scale)).intValue();
    } else {
      arrowhead.x = (absoluteTo.x>=absoluteFrom.x)?(absoluteTo.x-Node.NODE_WIDTH*Node.scale/2):(absoluteTo.x+Node.NODE_WIDTH*Node.scale/2);
      arrowback.x = (absoluteTo.x>=absoluteFrom.x)?(arrowhead.x-5*Node.scale):(arrowhead.x+Node.scale*5);
    }
    if(absoluteTo.x!=absoluteFrom.x) {
      delta = (absoluteTo.y-absoluteFrom.y)/length;
      arrowhead.y = new Double(absoluteTo.y-delta*(Node.NODE_WIDTH/2)).intValue();
      arrowback.y = new Double(absoluteTo.y-delta*(Node.NODE_WIDTH/2+5*Node.scale)).intValue();
    } else {
      arrowhead.y = (absoluteTo.y>=absoluteFrom.y)?(absoluteTo.y-Node.NODE_WIDTH/2):(absoluteTo.y+Node.NODE_WIDTH/2);
      arrowback.y = (absoluteTo.y>=absoluteFrom.y)?(arrowhead.y-5*Node.scale):(arrowhead.y+5*Node.scale);
    }
    pointArrowRight.x = arrowback.x-(arrowhead.y-arrowback.y);
    pointArrowRight.y = arrowback.y+(arrowhead.x-arrowback.x);
    pointArrowLeft.x = arrowback.x+(arrowhead.y-arrowback.y);
    pointArrowLeft.y = arrowback.y-(arrowhead.x-arrowback.x);
    pointCenter.x = (absoluteTo.x+absoluteFrom.x)/2;
    pointCenter.y = (absoluteTo.y+absoluteFrom.y)/2;
//    pointOneThird.x = (absoluteTo.x+absoluteFrom.x)/3;
//    pointOneThird.x = (absoluteTo.y+absoluteFrom.y)/3;
  }

  private void calculateAbsolutes() {
    absoluteFrom = new Point();
    absoluteTo = new Point();
    absoluteFrom.x = nodeFrom.getCenter().x+offsetFrom.x;
    absoluteFrom.y = nodeFrom.getCenter().y+offsetFrom.y;
    absoluteTo.x = nodeTo.getCenter().x+offsetTo.x;
    absoluteTo.y = nodeTo.getCenter().y+offsetTo.y;
  }
}
